﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;

#pragma warning disable IDE0130 // 命名空间与文件夹结构不匹配
namespace zykControl
#pragma warning restore IDE0130 // 命名空间与文件夹结构不匹配
{

    /// <summary>
    /// UI控件数据绑定代理,此对象是控件的数据绑定与模型类之间沟通的桥梁. UIProxy适用于极简模型,是UIProxySimple的升级版
    /// </summary>
    public class UIProxy<ModelType> where ModelType : new()
    {
        private readonly Form mForm;
        /// <summary>模型对象的实例</summary>
        public readonly ModelType Model = new();
        /// <summary>模型对象的隐藏实例,专用于控件的属性绑定</summary>
        private readonly ModelType mModel = new();
        private readonly PropertyDescriptorCollection mProps;

        /// <summary>
        /// 实例化一个数据绑定者,此对象是控件的数据绑定与模型类之间沟通的桥梁. UIProxy适用于极简模型,是UIProxySimple的升级版
        /// </summary>
        /// <param name="theForm">绑定到指定的窗体</param>
        /// <param name="AutoBind">是否自动执行数据绑定. 如果此值为True,则软件会立即执行绑定,或者在窗体触发Load事件时自动绑定</param>
        public UIProxy(Form theForm, bool AutoBind = false)
        {
            mForm = theForm;
            mProps = TypeDescriptor.GetProperties(typeof(ModelType));
            foreach (PropertyDescriptor dProp in mProps)
            {
                if (dProp.IsReadOnly) continue;
                dProp.SetValue(Model, dProp.GetValue(mModel));
                void dEvent(object? sender, EventArgs e) => dProp.SetValue(Model, dProp.GetValue(mModel));
                dProp.AddValueChanged(mModel, dEvent);
            }

            if (AutoBind)
            {
                if (mForm.Created)
                {
                    Bind();
                }
                else
                {
                    mForm.Load += FormLoadEvent;
                    mForm.FormClosed += FormClosedEvent;
                }
            }
        }

        private void FormLoadEvent(object? sender, EventArgs e)
        {
            Bind();
        }

        private void FormClosedEvent(object? sender, EventArgs e)
        {
            mForm.Load -= FormLoadEvent;
            mForm.FormClosed -= FormClosedEvent;
        }

        /// <summary>
        /// 查找与指定ModelName匹配的UIBinding组件(只返回找到的第一个组件)
        /// </summary>
        /// <param name="theModel"></param>
        /// <returns></returns>
        private UIBinding? FindUIBinding(object theModel)
        {
            var dFields = mForm.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public);
            foreach (var dField in dFields)
            {
                if (ReferenceEquals(dField.FieldType, typeof(UIBinding)) == false) continue;

                var dBinding = (UIBinding?)dField.GetValue(mForm);
                if (dBinding is not null && !string.IsNullOrEmpty(dBinding.ObjectName))
                {
                    if ((dBinding.ObjectName.ToLower() ?? "") == (theModel.GetType().Name.ToLower() ?? "")) return dBinding;
                }
            }

            return null;
        }

        /// <summary>
        /// 执行数据绑定. 在窗体创建完成后必须调用一次Bind方法(当AutoBind=True时不用调用). 多次执行Bind并不会引发异常,但不建议这样做
        /// </summary>
        public void Bind()
        {
            if (mModel == null) return;

            var dBinding = FindUIBinding(mModel);
            if (dBinding is null) return;

            var dControls = dBinding.GetControls();
            foreach (var dControl in dControls)
            {
                var dBindingList = dBinding.GetDataField(dControl);
                if (dBindingList is null || dBindingList.Length == 0)
                    continue;
                if (mCustomers.TryGetValue(dControl, out DlgUIBinder? value))
                {
                    value.Invoke(dControl, mModel, dBindingList);
                }
                else if (mAdapters.ContainsKey(dControl.GetType()))
                {
                    mAdapters[dControl.GetType()].Invoke(dControl, mModel, dBindingList);
                }
                else
                {
                    bool dIsFormat = false;
                    foreach (DataFieldItem dItem in dBindingList)
                    {
                        var dBindingItem = dControl.DataBindings[dItem.PropertyName];
                        if (dBindingItem is not null)
                            dControl.DataBindings.Remove(dBindingItem);
                        dIsFormat = !string.IsNullOrEmpty(dItem.FormatString);
                        try
                        {
                            dControl.DataBindings.Add(dItem.PropertyName, mModel, dItem.DataField, dIsFormat, dItem.UpdateMode, dItem.NullText, dItem.FormatString);
                        }
                        catch (Exception ex)
                        {
                            string dErr = "把 " + typeof(ModelType).Name + "." + dItem.DataField + " 绑定到控件 " + dControl.Name + "." + dItem.PropertyName
                                + " 时出错了: " + ex.Message + "\r\n\r\n" + " 调用堆栈信息:\r\n" + ex.StackTrace;
                            throw new Exception(dErr);
                        }
                    }
                }
            }
        }

        private static readonly Dictionary<Type, DlgUIBinder> mAdapters = [];
        /// <summary>
        /// 为一个控件类注册一个绑定者. 绑定者决定数据如何绑定到控件
        /// </summary>
        /// <param name="theType"></param>
        /// <param name="theAdapter"></param>
        public static void RegisterBinder(Type theType, DlgUIBinder theAdapter)
        {
            if (!mAdapters.TryAdd(theType, theAdapter)) mAdapters[theType] = theAdapter;
        }

        private readonly Dictionary<Control, DlgUIBinder> mCustomers = [];
        /// <summary>
        /// 为一个指定的控件自定义一个绑定. 
        /// </summary>
        /// <param name="theControl"></param>
        /// <param name="theAdapter"></param>
        public void CustomizeBinding(Control theControl, DlgUIBinder theAdapter)
        {
            if (!mCustomers.TryAdd(theControl, theAdapter)) mCustomers[theControl] = theAdapter;
        }

        /// <summary>
        /// 为模型指定名称的属性赋值
        /// </summary>
        /// <param name="thePropertyName"></param>
        /// <param name="theValue"></param>
        public void SetValue(string thePropertyName, object theValue)
        {
            var dProp = mProps[thePropertyName];
            dProp?.SetValue(mModel, theValue);
        }

        /// <summary>
        /// 使用指定对象的属性值,按同名规则为本对象的各属性赋值. theValueObject当中不可以出现本对象不存在的属性,并且各属性的类型也必须一致
        /// </summary>
        /// <param name="theValueObject"></param>
        public void SetValues(object theValueObject)
        {
            var dObjectProps = theValueObject.GetType().GetProperties();
            foreach (var dProp in dObjectProps)
            {
                if (dProp.CanRead == false) continue;
                if (dProp.GetIndexParameters().Length != 0) continue;

                var dThisProp = mProps[dProp.Name];
                dThisProp?.SetValue(mModel, dProp.GetValue(theValueObject, null));
            }
        }

        /// <summary>
        /// 使用指定对象的属性值,按同名规则为本对象的各属性赋值. 当theValueObject当中出现多余的属性时不会引发异常
        /// </summary>
        /// <param name="theValueObject"></param>
        public void SetValuesParse(object theValueObject)
        {
            var dObjectProps = theValueObject.GetType().GetProperties();
            foreach (var dProp in dObjectProps)
            {
                if (dProp.CanRead == false) continue;
                if (dProp.GetIndexParameters().Length != 0) continue;

                var dThisProp = mProps[dProp.Name];
                if (dThisProp is null) continue;

                dThisProp.SetValue(mModel, dProp.GetValue(theValueObject, null));
            }
        }

        /// <summary>
        /// 使用指定对象的字段值,按同名规则为本对象的各属性赋值. theValueObject当中不可以出现本对象不存在的字段,并且各属性的类型也必须一致
        /// </summary>
        /// <param name="theValueObject"></param>
        public void SetValuesFromField(object theValueObject)
        {
            var dFields = theValueObject.GetType().GetFields();
            foreach (var dField in dFields)
            {
                var dThisProp = mProps[dField.Name];
                dThisProp?.SetValue(mModel, dField.GetValue(theValueObject));
            }
        }

        /// <summary>
        /// 使用指定对象的字段值,按同名规则为本对象的各属性赋值. 当theValueObject当中出现多余的字段时不会引发异常
        /// </summary>
        /// <param name="theValueObject"></param>
        public void SetValuesFromFieldParse(object theValueObject)
        {
            var dFields = theValueObject.GetType().GetFields();
            foreach (var dField in dFields)
            {
                var dThisProp = mProps[dField.Name];
                if (dThisProp is null)
                    continue;
                dThisProp?.SetValue(mModel, dField.GetValue(theValueObject));
            }
        }

        /// <summary>
        /// 挂接一个属性变更事件
        /// </summary>
        /// <param name="thePropertyName"></param>
        /// <param name="theHandler"></param>
        public void HookValueChanged(string thePropertyName, EventHandler theHandler)
        {
            if (mModel == null) return;
            var dProp = mProps[thePropertyName];
            dProp?.RemoveValueChanged(mModel, theHandler);
            dProp?.AddValueChanged(mModel, theHandler);
        }

        /// <summary>
        /// 提交自上次以来的所有属性值更改,此方法由控制方改变了模型的值之后调用,如果不调用它,控制方对模型的修改不会同步到UI控件方,也不会触发变更事件
        /// </summary>
        public void SubmitChange()
        {
            foreach (PropertyDescriptor dProp in mProps)
            {
                var dValueOld = dProp.GetValue(Model);
                var dValueNew = dProp.GetValue(mModel);
                bool dIsChanged = false;
                if (!ReferenceEquals(dValueNew, dValueOld))
                {
                    dIsChanged = true;
                }
                else if (dValueOld is not null)
                {
                    if (dProp.GetType().IsValueType && dValueOld.Equals(dValueOld) == false)
                    {
                        dIsChanged = true;
                    }
                }

                if (dIsChanged) dProp.SetValue(mModel, dValueOld);
            }
        }
    }
}